/**
 * @file CGGetPicture.cpp
 * @author your name (you@domain.com)
 * @brief 
 * @version 0.1
 * @date 2021-05-12
 * 
 * @copyright Copyright (c) 2021
 * 
 */
#include <memory>
#include <thread>
#include <iostream>
#include "ffmpeg.h"

AVFormatContext *inputContext = nullptr;
AVFormatContext *outputContext = nullptr;
uint64_t lastReadPacktTime;

using namespace std;

static int interrupt_cb(void *ctx)
{
    int timeout = 3;
    if (av_gettime() - lastReadPacktTime > timeout * 1000 * 1000)
    {
        cout << "failed" << endl;
        return -1;
    }

    return 0;
}

void Init()
{
    av_register_all();
    avformat_network_init();
    av_log_set_level(AV_LOG_WARNING);
}

int OpenInput(const char *url)
{
    inputContext = avformat_alloc_context();
    lastReadPacktTime = av_gettime(); //Get the current time in microseconds.
    inputContext->interrupt_callback.callback = interrupt_cb;
    int ret = avformat_open_input(&inputContext, url, nullptr, nullptr); //函数中有一个循环，没循环一次都会去调用 callback ，如果函数返回 >=0 则继续，否则退出循环
    if (ret < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "Input file open input failed \n");
        return ret;
    }

    ret = avformat_find_stream_info(inputContext, nullptr);
    if (ret < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "Find input file stream inform failed\n");
    }
    else
    {
        av_log(NULL, AV_LOG_FATAL, "Open input file  %s success\n", url);
    }

    return ret;
}

shared_ptr<AVPacket> ReadPacketFromSource()
{
    shared_ptr<AVPacket> packet(static_cast<AVPacket *>(av_malloc(sizeof(AVPacket))), [&](AVPacket *p) {
        av_free_packet(p);
        av_freep(p);
    });

    av_init_packet(packet.get());
    lastReadPacktTime = av_gettime();
    int ret = av_read_frame(inputContext, packet.get());
    if (ret >= 0)
    {
        return packet;
    }
    else
    {
        return nullptr;
    }
}

int OpenOutPut(const char *outputUrl)
{
    int ret = avformat_alloc_output_context2(&outputContext, nullptr, "singlejpeg", outputUrl);
    if (ret < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "open output context failed\n");
        goto Error;
    }

    ret = avio_open2(&outputContext->pb, outputUrl, AVIO_FLAG_WRITE, nullptr, nullptr);
    if (ret < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "open avio failed");
        goto Error;
    }

    for (size_t i = 0; i < inputContext->nb_streams; i++)
    {
        if (inputContext->streams[i]->codec->codec_type == AVMediaType::AVMEDIA_TYPE_AUDIO)
        {
            continue;
        }

        AVStream *stream = avformat_new_stream(outputContext, inputContext->streams[i]->codec->codec);
        ret = avcodec_copy_context(stream->codec, inputContext->streams[i]->codec);
        if (ret < 0)
        {
            av_log(NULL, AV_LOG_ERROR, "copy coddec context failed");
            goto Error;
        }
    }

    ret = avformat_write_header(outputContext, nullptr);
    if (ret < 0)
    {
        av_log(NULL, AV_LOG_ERROR, "format write header failed");
        goto Error;
    }
    av_log(NULL, AV_LOG_FATAL, " Open output file success %s\n", outputUrl);
    return ret;

Error:
    if (outputContext)
    {
        for (size_t i = 0; i < outputContext->nb_streams; i++)
        {
            avcodec_close(outputContext->streams[i]->codec);
        }

        avformat_close_input(&outputContext);
    }
    return ret;
}

void CloseInput()
{
    if (inputContext)
    {
        avformat_close_input(&inputContext);
    }
}

void CloseOutput()
{
    if (outputContext != nullptr)
    {
        int ret = av_write_trailer(outputContext);
        for (size_t i = 0; i < outputContext->nb_streams; i++)
        {
            AVCodecContext *codecContext = outputContext->streams[i]->codec;
            avcodec_close(codecContext);
        }

        avformat_close_input(&outputContext);
    }
}

int WritePacket(shared_ptr<AVPacket> packet)
{
    return av_interleaved_write_frame(outputContext, packet.get());
}

int InitDecodeContext(AVStream *inputStream)
{
    auto codecId = inputStream->codec->codec_id;
    auto codec = avcodec_find_decoder(codecId);
    if (!codec)
    {
        return -1;
    }

    int ret = avcodec_open2(inputStream->codec, codec, NULL);
    return ret;
}

int initEncoderCodec(AVStream *inputStream, AVCodecContext **encodeContext)
{
    AVCodec *picCodec;
    picCodec = avcodec_find_encoder(AV_CODEC_ID_MJPEG);
    (*encodeContext) = avcodec_alloc_context3(picCodec);
    (*encodeContext)->codec_id = picCodec->id;
    (*encodeContext)->time_base.num = inputStream->codec->time_base.num;
    (*encodeContext)->time_base.den = inputStream->codec->time_base.den;
    (*encodeContext)->pix_fmt = *(picCodec->pix_fmts);
    (*encodeContext)->width = inputStream->codec->width;
    (*encodeContext)->height = inputStream->codec->height;
    int ret = avcodec_open2((*encodeContext), picCodec, NULL);
    if (ret < 0)
    {
        cout << "open video codec failed" << endl;
        return ret;
    }
    return 1;
}

bool Decode(AVStream *inputStream, AVPacket *packet, AVFrame *frame)
{
    int gotFrame = 0;
    auto hr = avcodec_decode_video2(inputStream->codec, frame, &gotFrame, packet);
    if (hr >= 0 && gotFrame != 0)
    {
        return true;
    }

    return false;
}

shared_ptr<AVPacket> Encode(AVCodecContext *encodeContext, AVFrame *frame)
{
    int gotOutput = 0;
    shared_ptr<AVPacket> pkt(static_cast<AVPacket *>(av_malloc(sizeof(AVPacket))),
                             [](AVPacket *p) {
                                 av_packet_free(&p);
                                 av_log(NULL, AV_LOG_ERROR, "free AVPacket \n");
                                 av_freep(&p); //`*ptr = NULL` is safe and leads to no action.
                             });

    av_init_packet(pkt.get());
    pkt->data = NULL;
    pkt->size = 0;

#if 0
    int ret = EAGAIN;
    while (ret == EAGAIN) //如果 返回值 EAGAIN 则继续
    {
        ret = avcodec_receive_packet(encodeContext, pkt.get());
        if (ret == 0)
        { //如果 返回值 等于0 退出
            break;
        }
    }

    ret = EAGAIN;
    while (ret == EAGAIN)
    {
        ret = avcodec_send_frame(encodeContext, frame);
        if (ret >= 0)
        {
            break;
        }
    }

    if (ret == 0)
    {
        return pkt;
    }

    return nullptr;

#else
    int ret = avcodec_encode_video2(encodeContext, pkt.get(), frame, &gotOutput);
    if (ret >= 0 && gotOutput)
    {
        return pkt;
    }
    else
    {
        return nullptr;
    }
#endif
}

int main(void)
{
    Init();
    AVCodecContext *encodContext = nullptr;
    AVFrame *videoFrame = nullptr;
    int ret = OpenInput("rtsp://192.168.8.142:8554/sintel.264");
    if (ret >= 0)
    {
        cout << "open input success " << __LINE__ << endl;
        ret = OpenOutPut("D:\\test\\test.jpg");
    }

    if (ret < 0)
    {
        cout << "open input failed " << __LINE__ << endl;
        goto Error;
    }
    InitDecodeContext(inputContext->streams[0]);
    videoFrame = av_frame_alloc();
    initEncoderCodec(inputContext->streams[0], &encodContext);
    while (true)
    {
        auto packet = ReadPacketFromSource();
        if (packet && packet->stream_index == 0)
        {
            if (Decode(inputContext->streams[0], packet.get(), videoFrame))
            {
                auto packetEncode = Encode(encodContext, videoFrame);
                if (packetEncode != nullptr)
                {
                    ret = WritePacket(packetEncode);
                    if (ret >= 0)
                    {
                        break;
                    }
                }
            }
        }
    }

    cout << "Get Picture End" << endl;
    av_frame_free(&videoFrame);
    avcodec_free_context(&encodContext);

Error:
    CloseInput();
    CloseOutput();

    cout << "end " << __LINE__ << endl;
    while (true)
    {
        this_thread::sleep_for(chrono::seconds(100));
    }
    return 0;
}
